package jastgen.generator;

import java.io.PrintWriter;
import java.util.Set;
import java.util.TreeSet;

import jastgen.CompileException;
import jastgen.nodes.ClassDef;
import jastgen.nodes.Field;
import jastgen.nodes.Module;
import jastgen.nodes.Type;

class VoidVisitorImplBodyGenerator implements BodyGenerator {

	private final Set<String> nodeTypes;
	private final Module node;

	VoidVisitorImplBodyGenerator(
			final Set<String> nodeTypes,
			final Module node
		) throws CompileException {

		this.nodeTypes = nodeTypes;
		this.node = node;
	}

	@Override
	public void generate(
			final PrintWriter out)
	{
		generateVisitorBody(out, node);
	}

	private void generateVisitorBody(
			final PrintWriter out,
			final Module node)
	{
		// TODO: 2つともbooleanのフラグで良いが、参照渡しできないので
		final Set<String> nodesToVisit = new TreeSet<String>();
		final Set<String> usedContainers = new TreeSet<String>();

		out.println("public abstract class VoidVisitorImpl implements VoidVisitor {");

		// クラスごとのメソッドを出力
		for(final ClassDef c : node.getClassDefs()) {
			generateVisitorBodyClass1(
					c,
					usedContainers,
					nodesToVisit);
		}

		// ノードクラスを要素とするコンテナの全要素をvisitするメソッドを出力
		// TODO: リストではなくフラグでOK
		if(usedContainers.size() != 0)
			printVisitContainer(out);

		// コンテナ以外のノードクラスをvisitするメソッドを出力
		// TODO: リストではなくフラグでOK
		// TODO: 無条件に出力して構わないのでは?
		if(nodesToVisit.size() != 0)
			printVisit(out);

		out.println("}");
	}

	private void printVisit(
			final PrintWriter out)
	{
		out.println();
		out.println("	public final <N extends Node> void inNode(");
		out.println("			final N n)");
		out.println("	{");
		out.println("		if(n != null)");
		out.println("			n.visit(this);");
		out.println("	}");
	}

	private void printVisitContainer(
			final PrintWriter out)
	{
		out.println();
		out.println("	public final <N extends Node> void inNode(");
		out.println("			final Collection<N> ns)");
		out.println("	{");
		out.println("		if(ns != null)");
		out.println("			for(final N n : ns)");
		out.println("				inNode(n);");
		out.println("	}");
	}

	private void generateVisitorBodyClass1(
			final ClassDef node,
			final Set<String> usedContainers,
			final Set<String> nodesToVisit)
	{
		for(final Field f : node.getFields()) {
			generateVisitorBodyMember(
					f,
					usedContainers,
					nodesToVisit);
		}
	}

	static boolean importCollection(
			final Set<String> nodeTypes,
			final Module node)
	{
		for(final ClassDef c : node.getClassDefs())
			for(final Field f : c.getFields())
				if(importCollection(nodeTypes, f))
					return true;
		return false;
	}

	// ノードクラスを要素とするコンテナならばtrue
	//
	// VoidVisitorAdapter、NodeReplacer、TreePrinterの3つは大部分が共通
	private static boolean importCollection(
			final Set<String> nodeTypes,
			final Field node)
	{
		final Type type = node.getType();
		if(type.maybeContainerToVisit()) {
			final String inner = type.getOnlyParam().toString();
			if(nodeTypes.contains(inner)) {
				// 型パラメータが1つで、ノードクラスであり、
				// 外側のクラスはcontainerPrefixesに含まれる場合。
				return true;
			}
		}
		return false;
	}

	// VoidVisitorImpl、VoidVisitorAdapter、VisitorImpl、NodeReplacer、TreePrinterの5つは大部分が共通
	private void generateVisitorBodyMember(
			final Field node,
			final Set<String> usedContainers,
			final Set<String> nodesToVisit)
	{
		final Type type = node.getType();

		if(type.maybeContainerToVisit()) {
			final String inner = type.getOnlyParam().toString();
			if(nodeTypes.contains(inner)) {
				// 型パラメータが1つで、ノードクラスであり、
				// 外側のクラスはcontainerPrefixesに含まれる場合。
				usedContainers.add(inner);
				nodesToVisit.add(inner);
				return;
			}
		}

		if(nodeTypes.contains(type.toString())) {
			nodesToVisit.add(type.toString());
			return;
		}

		return;
	}
}
